﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

namespace System.IO
{
    public static class StreamEx
    {
        /// <summary>
        /// 写入简单的装箱类型数据
        /// （值类型数据和string类型数据）
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public static long WriteSimplePackingData(this Stream stream, object data)
        {
            return stream.Write(GetByteBySimplePackingData(data));
        }

        /// <summary>
        /// 将数组写入流
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        public static long Write(this Stream stream, byte[] data)
        {
            var position = stream.Position;

            if (data != null)
                stream.Write(data, 0, data.Length);

            return position;
        }

        /// <summary>
        /// 将另一条流中的数据整个写入到当前流中
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="source"></param>
        public static void Write(this Stream stream, Stream source, long offset, long count)
        {
            byte[] temp = new byte[1024 * 10];
            if (source.CanSeek)
                source.Seek(offset, SeekOrigin.Begin);

            long sum = 0;
            while (true)
            {
                int temp_count = source.Read(temp, 0, (int)Math.Min(temp.Length, count - sum));
                if (temp_count == 0)
                    break;

                stream.Write(temp, 0, temp_count);

                sum += temp_count;
                if (sum >= count)
                    break;
            }
        }

        /// <summary>
        /// 获取简单数据的长度
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static int GetSimplePackingTypeLength(Type type)
        {
            if (type == null)
                return 0;


            if ((!type.IsValueType) && type != typeof(string))
                throw new Exception($"不支持的数据类型 {type}");

            if (type == typeof(string))
                return sizeof(long);

            return Marshal.SizeOf(type);
        }

        /// <summary>
        /// 根据简单类型的装箱数据获取数据数组
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static byte[] GetByteBySimplePackingData(object obj)
        {
            if (obj == null)
                return null;

            var obj_type = obj.GetType();
            if ((!obj_type.IsValueType) && obj_type != typeof(string))
                throw new Exception($"不支持的数据类型 {obj.GetType()}");

            byte[] back_data = null;

            if (obj_type == typeof(string))
            {
                var temp = Encoding.UTF8.GetBytes((string)obj);
                back_data = new byte[temp.Length + sizeof(int)];
                var length_data = BitConverter.GetBytes(temp.Length);

                Array.Copy(length_data, back_data, length_data.Length);
                Array.Copy(temp, 0, back_data, length_data.Length, temp.Length);
            }
            else
            {
                switch (Type.GetTypeCode(obj_type))
                {
                    case TypeCode.Boolean:
                        back_data = BitConverter.GetBytes((bool)obj);
                        break;
                    case TypeCode.Byte:
                        back_data = BitConverter.GetBytes((byte)obj);
                        break;
                    case TypeCode.Char:
                        back_data = BitConverter.GetBytes((char)obj);
                        break;
                    case TypeCode.DateTime:
                        back_data = BitConverter.GetBytes(((DateTime)obj).Ticks);
                        break;
                    case TypeCode.Double:
                        back_data = BitConverter.GetBytes((double)obj);
                        break;
                    case TypeCode.Int16:
                        back_data = BitConverter.GetBytes((Int16)obj);
                        break;
                    case TypeCode.Int32:
                        back_data = BitConverter.GetBytes((Int32)obj);
                        break;
                    case TypeCode.Int64:
                    case TypeCode.Decimal:
                        back_data = BitConverter.GetBytes((Int64)obj);
                        break;
                    case TypeCode.SByte:
                        back_data = BitConverter.GetBytes((sbyte)obj);
                        break;
                    case TypeCode.Single:
                        back_data = BitConverter.GetBytes((Single)obj);
                        break;
                    case TypeCode.UInt16:
                        back_data = BitConverter.GetBytes((UInt16)obj);
                        break;
                    case TypeCode.UInt32:
                        back_data = BitConverter.GetBytes((UInt32)obj);
                        break;
                    case TypeCode.UInt64:
                        back_data = BitConverter.GetBytes((UInt64)obj);
                        break;


                    default:
                        break;
                }
            }

            return back_data;
        }

        public static object GetSimplePackingDataByByte(byte[] data, Type type)
        {
            object back_data = null;

            switch (Type.GetTypeCode(type))
            {
                case TypeCode.Boolean:
                    back_data = BitConverter.ToBoolean(data, 0);
                    break;
                case TypeCode.Byte:
                    back_data = data?[0];
                    break;
                case TypeCode.Char:
                    back_data = (char)data?[0];
                    break;
                case TypeCode.DateTime:
                    back_data = new DateTime(BitConverter.ToInt64(data, 0));
                    break;
                case TypeCode.Double:
                    back_data = BitConverter.ToDouble(data, 0);
                    break;
                case TypeCode.Int16:
                    back_data = BitConverter.ToInt16(data, 0);
                    break;
                case TypeCode.Int32:
                    back_data = BitConverter.ToInt32(data, 0);
                    break;
                case TypeCode.Int64:
                case TypeCode.Decimal:
                    back_data = BitConverter.ToInt64(data, 0);
                    break;
                case TypeCode.SByte:
                    back_data = data?[0];
                    break;
                case TypeCode.Single:
                    back_data = BitConverter.ToSingle(data, 0);
                    break;
                case TypeCode.UInt16:
                    back_data = BitConverter.ToUInt16(data, 0);
                    break;
                case TypeCode.UInt32:
                    back_data = BitConverter.ToUInt32(data, 0);
                    break;
                case TypeCode.UInt64:
                    back_data = BitConverter.ToUInt64(data, 0);
                    break;


                default:
                    break;
            }

            return back_data;
        }

        /// <summary>
        /// 根据byte数组获取书JSU
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="data"></param>
        /// <returns></returns>
        public static T GetByByte<T>(byte[] data)
        {
            var size = Marshal.SizeOf<T>();
            var ptr = Marshal.AllocHGlobal(size);

            T obj = Marshal.PtrToStructure<T>(ptr);

            Marshal.FreeHGlobal(ptr);

            return obj;
        }

        /// <summary>
        /// 从数据流中读取出指定长度的数据
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public static Byte[] Read(this Stream stream, long count)
        {
            byte[] temp = new byte[count];

            int sum = 0;
            while (true)
            {
                var temp_count = stream.Read(temp, sum, temp.Length - sum);

                if (temp_count == 0)
                    throw new Exception("无法继续读取数据");

                sum += temp_count;
                if (sum >= temp.Length)
                    break;
            }

            return temp;
        }

        /// <summary>
        /// 从流中读取一个结构体
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="stream"></param>
        /// <returns></returns>
        public static T Read<T>(this Stream stream)
            where T : struct
        {
            var size = Marshal.SizeOf<T>();
            var bytes = stream.Read(size);
            var ptr = Marshal.AllocHGlobal(size);

            Marshal.Copy(bytes, 0, ptr, size);
            T t = Marshal.PtrToStructure<T>(ptr);
            Marshal.FreeHGlobal(ptr);

            return t;
        }
    }
}
